package com.google.springfacestools.config;

import java.lang.reflect.Field;
import java.lang.reflect.Method;

import javax.faces.context.FacesContext;

import org.springframework.aop.scope.ScopedProxyUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.AutowireCandidateQualifier;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.stereotype.Component;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.util.ReflectionUtils.FieldCallback;
import org.springframework.util.ReflectionUtils.MethodCallback;

import com.google.springfacestools.Factory;
import com.google.springfacestools.Out;

@Component
public class SpringFacesToolsConfigurer implements BeanFactoryPostProcessor {

	@Override
	public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
		
		String[] beanNames = beanFactory.getBeanDefinitionNames();
		BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory;
		configureFacesContextBean(registry);
		for (int i = 0; i < beanNames.length; i++) {
			String beanName = beanNames[i];
			Class<?> beanClass = beanFactory.getType(beanName);
			if(beanClass != null) {
				//configureBeanClassFactoryMethods(beanName, beanClass, registry);
				configureBeanClassOutFields(beanName, beanClass, registry);	
			}
		}
		
	}
	
	protected void configureBeanClassFactoryMethods(final String beanName, Class<?> beanClass, final BeanDefinitionRegistry registry) {
		ReflectionUtils.doWithMethods(beanClass, new MethodCallback() {
			public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException {				
				Factory factory = method.getAnnotation(Factory.class);
				if(factory != null) {
					if(method.getReturnType() == void.class)
						throw new IllegalArgumentException("Factory Methods Cannot Return Void! " + method.getName());
					if(method.getParameterTypes().length > 0 )
						throw new IllegalArgumentException("Factory Methods Cannot Have Parameters! " + method.getName());

					GenericBeanDefinition def = new GenericBeanDefinition();
					def.setBeanClass(method.getReturnType());
					def.setFactoryBeanName(beanName);
					def.setFactoryMethodName(method.getName());
					def.setScope(factory.scope());
					if(StringUtils.hasText(factory.qualifier())) {
						def.addQualifier(new AutowireCandidateQualifier(Qualifier.class, factory.qualifier()));
					}
					String newBeanName = factory.value();
					if(!StringUtils.hasText(newBeanName)) {
						newBeanName = method.getName();
					}
					boolean proxyTargetClass = true;

					if(def.getBeanClass().isEnum() || (def.getBeanClass().isArray() && def.getBeanClass().getComponentType().isEnum())) {
						proxyTargetClass = false;
					}
					
					BeanDefinitionHolder proxyDefinitionHolder = ScopedProxyUtils.createScopedProxy(new BeanDefinitionHolder(def, newBeanName), registry, proxyTargetClass);
					registry.registerBeanDefinition(proxyDefinitionHolder.getBeanName(), proxyDefinitionHolder.getBeanDefinition());
				}
			}
		});
	}

	protected void configureBeanClassOutFields(String beanName, Class<?> beanClass, final BeanDefinitionRegistry registry) {
		ReflectionUtils.doWithFields(beanClass, new FieldCallback() {
			public void doWith(Field field) throws IllegalArgumentException, IllegalAccessException {
				Out out = field.getAnnotation(Out.class);
				if(out != null) {
					GenericBeanDefinition def = new GenericBeanDefinition();
					def.setBeanClass(field.getType());
					def.setScope(out.scope());
					Qualifier qualifier = field.getAnnotation(Qualifier.class);
					if(qualifier != null) {
						def.addQualifier(new AutowireCandidateQualifier(Qualifier.class, qualifier.value()));
					}
					String newBeanName = out.value();
					if(!StringUtils.hasText(newBeanName)) {
						newBeanName = field.getType().getName();
					}
					BeanDefinitionHolder proxyDefinitionHolder = ScopedProxyUtils.createScopedProxy(new BeanDefinitionHolder(def, newBeanName), registry, true);
					registry.registerBeanDefinition(proxyDefinitionHolder.getBeanName(), proxyDefinitionHolder.getBeanDefinition());
				}
			}
		});	
	}
	
	protected void configureFacesContextBean(BeanDefinitionRegistry registry) {
		GenericBeanDefinition def = new GenericBeanDefinition();
		def.setBeanClass(FacesContext.class);
		def.setFactoryMethodName("getCurrentInstance");
		def.setScope("request");
		BeanDefinitionHolder proxyDefinitionHolder = ScopedProxyUtils.createScopedProxy(new BeanDefinitionHolder(def, "facesContext"), registry, true);
		registry.registerBeanDefinition(proxyDefinitionHolder.getBeanName(), proxyDefinitionHolder.getBeanDefinition());
	}
	
}
